دانشکده ی علوم ریاضی داده ساختارها و الگوریتم ها ۸ مهر ۹ جلسه ی ۱۰: الگوریتم مرتب سازی سریع مدر س: دکتر شهرام خزاي ی نگارنده: محمد امین ادر یسی و سینا منصور لکورج ۱ شرح الگور یتم الگوریتم مرتب سازی سریع ۱ فرا یند تقسیم و حل را به کار می گیرد. سه مرحله ی مربوطه در زیر شرح داده شده اند: تقسیم: در این مرحله ا رایه ی A[p..r] به سه زیر ا رایه به صورت [r..۱ A[q] A[q + و [۱ A[p..q تقسیم می شود به گونه ای که زیرا رایه ی اول شامل اعضایی از ا رایه ی اصلی است که از A[q] بزرگترند و زیرا رایه ی سوم شامل اعضای کوچکتر یا مساوی با A[q] است. حل: در مرحله ی حل با فراخوانی بازگشتی تابع مرتب سازی سریع دو زیرا رایه ی تولید شده در مرحله تقسیم یعنی ۱..r] A[q + و ۱] A[p..q را مرتب می کنیم. ترکیب: چون که با فراخوانی بازگشتی تابع مرتب سازی سریع زیرا رایه ها در نهایت به صورت مجموعه های تک عضوی مرتب شده ای نسبت به هم تجزیه می شوند پس نیازی به ترکیب کردن نتایج نداریم. شبه کد مربوط به تابع مرتب سازی سریع در زیر ا ورده شده است: a Quicksort function QuickSort(A, p, r) if p < r then q Partition(A, p, r) Quicksort(A, p, q 1) Quicksort(A, q + 1, r) b Partition function Partition(A, p, r) x A[r] i p 1 for j p to r 1 do if A[j] x then i i + 1 A[i] A[j] A[i + 1] A[r] return i + 1 کار تقسیم بندی ا رایه را در هر فراخوانی تابع تقسیم بندی انجام می دهد که در بالا شبه کد ا ن نمایش داده شده است. خروجی تابع اندیسی از ا رایه است که کلیدهای مربوط به اندیس های بعد از ا ن بزرگتر و کلیدهای مربوط به ۱ Quick sort Partition ۱۰-۱
اندیس های قبل از ا ن کوچکتر از کلید این اندیس هستند. این تابع برای مقایسه همواره عنصر ا خر ا رایه ی ورودی را به عنوان محور ۳ انتخاب می کند. در هنگام اجرای این تابع ا رایه ی ورودی به چهار ناحیه تقسیم می شود که طبق شبه کد اگر j و i را در هر تکرار در نظر بگیریم به صورت زیر هستند (در اینجا x همان A[r] است): برای اندیس هایی که p k i داریم A[k] x برای اندیس هایی که ۱ j i + ۱ k داریم A[k] > x اندیس هایی که از ۱ j بزرگتر و از r کوچکترند نیز بررسی نشده اند ناحیه چهارم را نیز می توان به عنصر محور که همان A[r] x است اختصاص داد در شکل زیر این چهار ناحیه نمایش داده شده اند: } {{ } x } {{ } >x }{{}}{{} unchecked pivot برای درک بهتر نحوه ی عملکرد این تابع ا ن را بر روی یک ا رایه ی دلخواه اجرا می کنیم: مثال ۱ اجرای تابع تقسیم روی ا رایه ی ۵] :A[۴, ۸, ۹, ۷, ۳, ۶, ۱, ۰,, i j, p r ۴ ۳ ۹ ۷ ۸ ۶ ۱ ۰ ۵ ۳ pivot ۱۰-
۴ ۳ ۹ ۷ ۸ ۶ ۱ ۰ ۵ ۴ ۳ ۱ ۷ ۸ ۶ ۹ ۰ ۵ ۴ ۳ ۱ ۰ ۸ ۶ ۹ ۷ ۵ ۴ ۳ ۱ ۰ ۶ ۹ ۷ ۸ ۵ p i r ۴ ۳ ۱ ۰ ۵ ۹ ۷ ۸ ۶ صحت الگور یتم برای نشان دادن صحت الگوریتم مرتب سازی سریع باید نشان داد که تابع تقسیم به درستی کار می کند. ابتدا ناوردای حلقه را تعریف می کنیم: ناوردا: در ابتدای اجرای حلقه ی for کلید مربوط به اندیس های بین p و i از A[r] کوچکتر و کلید مربوط به اندیس های بین + ۱ i و ۱ j نیز از A[r] بزرگترند. مراحل اثبات صحت الگوریتم به شرح زیر است: ا غاز: قبل از اولین اجرای حلقه چون ۱ p i است و j p پس زیرا رایه های A[p..i] و ۱] ۱..j A[i + تهی هستند در نتیجه شرایط ناوردایی برقرار است. نگهداری: در این مرحله دو حالت را در نظر می گیریم. اگر A[j] > x باشد که در این صورت تنها کاری که انجام می شود این است که j یک واحد افزایش می یابد و باز ناوردایی حلقه برقرار است چون در ابتدای شروع دوباره ی حلقه اندیس های + ۱ i تا ۱ j همگی از x بزرگترند. حال اگر A[j] x باشد در این صورت i یک واحد افزایش یافته و جای A[j] و A[i] عوض می شود. در این حالت همچنان اندیس های بین p و i از x کوچکترند همچنین A[i] (i جدید در اینجا منظور است) که از x بزرگتر است در مکان j قرار می گیرد. با افزایش j در ا غاز اجرای بعدی حلقه می توان گفت اعضای با اندیس های + ۱ i تا ۱ j از x بزرگترند. پس ناوردایی برقرار است. پایان: در ا خرین اجرای حلقه ی j r for است پس از ا نجایی که ناوردایی برقرار است می توان گفت که زیرا رایه ی A[p..i] شامل اعضای کوچکتر یا مساوی x است و زیرا رایه ی [۱ r..۱ A[i + شامل اعضای بزرگتر از x است و عضو با اندیس r نیز محور است در واقع ا رایه ی اصلی به سه زیرا رایه تقسیم شده است و نشان می دهد که تابع به درستی کار می کند. ۱۰-۳
۳ پیچیدگی الگور یتم می دانیم که تابع تقسیم به دلیل داشتن یک حلقه ی for دارای مرتبه ی زمانی Θ(n) است زمان اجرا در حقیقت به افرازهای حاصل از تابع تقسیم بستگی دارد. حال زمان اجرای الگوریتم را در بهترین و بدترین حالت محاسبه می کنیم: ۱.۳ بدتر ین حالت بدترین حالت را می توان حالتی در نظر گرفت که در ا ن به ازای هر بار فراخوانی یکی از افرازهای حاصل از تقسیم تهی باشد. در این صورت می توان زمان اجرا را به صورت بازگشتی زیر نوشت: T (n) T (۰) + T (n ۱) + Θ(n) که در اینجا Θ(n) هزینه مرحله ی ترکیب است. با حل این رابطه ی بازگشتی در می یابیم که ) T (n) Θ(n.۳ بهتر ین حالت بهترین حالت را می توان حالتی در نظر گرفت که برخلاف مورد قبل در هر بار فراخوانی افرازها دارای طول برابر باشند. در این حالت داریم: T (n) T ( n ۱ ) + Θ(n) ۱ در اینجا تا ثیری در جواب این رابطه ی بازگشتی ندارد در واقع این رابطه تفاوتی با می دانیم که وجود عدد ثابت رابطه ی Θ(n) T (n) T (/n) + ندارد که از قضیه ی اصلی می دانیم که دارای جواب (n Θ(n log است. در ادامه زمان اجرا را به طور میانگین محاسبه می کنیم و نشان داده می شود که این زمان اجرا به بهترین حالت نزدیک تر است تا بدترین حالت. برای داشتن شهودی نسبت به این مطلب یعنی نزدیک یودن حالت میانگین به بهترین حالت می توان فرض کرد که در هر بار اجرای تابع تقسیم ا رایه به نسبت ۱ به ۹۹ تقسیم می شود یعنی داریم: n T (n) T ( ۱۰۰ ) + T ( ۹۹n ۱۰۰ ) + Θ(n) در اینجا می توان به روشی مشابه مثالی از جلسه که نسبت ها می رسیم. ۳ و ۱ ۳ بود این رابطه را حل کرد که به جواب (n Θ(n log ۴ مرتب سازی سریع تصادفی ما تمایل داریم که این زمان اجرا وابستگی کمی به ورودی داشته باشد. برای این کار دو راه حل وجود دارد یکی اینکه قبل از اجرای الگوریتم ورودی را به صورت تصادفی مرتب کنیم و دیگری اینکه در تابع تقسیم محور را به طور تصادفی انتخاب کنیم که این کار منجر به ایجاد الگوریتم مرتب سازی سریع تصادفی ۴ می شود که در ادامه شبه کد ا ن ا مده است: ۴ Randomized Quick Sort ۱۰-۴
c Randomized-Quicksort -)A, p, r( p < r q -(A, p, r) -(A, p, q 1) -(A, q + 1, r) d Randomized-Partition -)A, p, r( i (p, r) A[i] A[r] (A, p, r) ۱.۴ محاسبه ی متوسط زمان اجرا حال متوسط زمان اجرا را به دست می ا وریم. در هر بار اجرای کل الگوریتم می توان گفت که حداکثر n بار تابع تقسیم فراخوانی می شود. در هر بار فراخوانی این تابع نیز تعدادی زمان اجرا از مرتبه ی (۱)Θ داریم و حلقه ی forای که زمان اجرای ا ن به تعداد مقایسه هایی بستگی دارد که در ا ن انجام می شود. از ا نجایی که با الگوریتم مرتب سازی سریع تصادفی کار می کنیم تعداد مقایسه ها در هر بار اجرای تابع تقسیم تصادفی است اگر تعداد کل مقایسه ها را با نماد X نشان دهیم داریم: T (n) Θ(n) + X که چون برای متوسط زمان اجرا باید امید ریاضی را به دست ا وریم پس: E[T (n)] Θ(n) + E[X] ۱.۱.۴ محابسه ی امید ریاضی تعداد مقایسه ها برای این کار ابتدا اعضای ا رایه ی nتایی ورودی را به صورت z ۱, z,, z n در نظر می گیریم و مجموعه ی i,j Z } j z} i, z ۱+i,, z را به عنوان مجموعه ی اعضای بین i و j تعریف می کنیم. حال اگر پیشامد مقایسه شدن دو عضو را با C i,j نشان دهیم متغیر شاخص X i,j را برای این پیشامد به صورت زیر می توان تعریف کرد: X i,j { اگر پیشامد Ci,j رخ دهد ۱ در غیر این صورت ۰ که ا ن را به اختصار به صورت ) i,j X i,j I(C نشان می دهند. از ا نجایی مقایسه ی بین دو عنصر زمانی رخ می دهد که یکی از ا ن دو به عنوان محور انتخاب شده باشد و پس از مقایسه نیز دیگر امکان مقایسه ی دوباره وجود ندارد پس می توان نوشت: X n i۱ ji+۱ X i,j در نتیجه برای محاسبه ی امید ریاضی X از خطی بودن امید ریاضی استفاده کرده و داریم: ۱۰-۵
E[X] E n i۱ ji+۱ n i۱ ji+۱ n i۱ ji+۱ X i,j E[X i,j ] Pr(C i,j ) برای اینکه احتمال مقایسه شدن دو عضو z j و z i را محاسبه کنیم فرض می کنیم که مجموعه ی ورودی به تابع تقسیم دارای زیر مجموعه ی Z i,j باشد در این صورت اگر عضو محور کوچکتر از z i یا بزرگتر از z j باشد در این صورت نمی توان گفت که این دو مقایسه می شوند یا نه و باید مراحل بعدی را هم بررسی کرد. پس فرض می کنیم که به مرحله ای رسیده ایم که در ا ن خود مجموعه ی Z i,j ورودی تابع تقسیم است. در این حالت فقط در صورتی این دو با هم مقایسه می شوند که یکی از این دو به عنوان محور انتخاب شود و چون فرض کردیم اعضا به صورت یکنواخت انتخاب می شوند می توان نوشت: با جایگذاری در E[X] داریم: (انتخاب شدن j به عنوان محور) Pr + (انتخاب شدن i به عنوان محور) Pr Pr(C i,j ) ۱ j i + ۱ + ۱ j i + ۱ j i + ۱ E[X] < n i۱ ji+۱ n i i۱ k۱ n i۱ k۱ j i + ۱ k + ۱ k O(log n) i۱ O(n log n) پس می توان نوشت: E[T (n)] Θ(n) + O(n log n) O(n log n) از طرفی چون بهترین حالت که به ا ن اشاره شد دارای مرتبه ی زمانی (n O(n log است پس به طور قوی تر کران پایین ا ن به صورت (n Ω(n log است که از این مطلب و اثبات بالا می توان نتیجه گرفت که: E[T (n)] Θ(n log n) ۱۰-۶